{************************************************}
{                                                }
{   Turbo Vision File Manager Demo               }
{   Copyright (c) 1992 by Borland International  }
{                                                }
{************************************************}

{$X+,V-}

unit Globals;

interface

uses Objects, Drivers, App, Views, Menus, Dialogs, Dos, DragDrop;

type

  TConfigRec = record
    FileMask: string[12];
    ShowHidden: Word;
    SortField: Word;
    SortDir: Word;
    DisplayCase: Word;
    DisplayFields: Word;
    Video: Word;
  end;

{ Event.InfoPtr points to a TScanInfo record if the when cmScanComplete
  is broadcast }

  PScanInfo = ^TScanInfo;
  TScanInfo = record
    ScanCount: LongInt;
    ScanBytes: LongInt;
  end;

  PTextCollection = ^TTextCollection;
  TTextCollection = object(TCollection)
    procedure FreeItem(Item: pointer); virtual;
  end;

  PProtectedStream = ^TProtectedStream;
  TProtectedStream = object(TBufStream)
    procedure Error(Code, Info: Integer); virtual;
  end;

  { THCStatusLine is a help context sensitive status line }

  PHCStatusLine = ^THCStatusLine;
  THCStatusLine = object(TStatusLine)
    function Hint(AHelpCtx: Word): String; virtual;
  end;

  { record used to identify a file by name only }
  PFileNameRec = ^TFileNameRec;
  TFileNameRec = record
    Dir: DirStr;
    Name: NameStr;
    Ext: ExtStr;
  end;

  { represents a single file in a file list }
  PFileRec = ^TFileRec;
  TFileRec = object(TObject)
    Tagged: Boolean;
    Name: NameStr;
    Ext: ExtStr;
    Attr: Byte;
    Size: Longint;
    Time: Longint;
    constructor Init(const S: SearchRec);
    procedure Toggle;
  end;

  { moving view while files are being dragged }
  PFileMover = ^TFileMover;
  TFileMover = object(TMover)
    procedure Draw; virtual;
  end;

  { sorted collection that sorts according to the ConfigRec settings. }
  PFileList = ^TFileList;
  TFileList = object(TSortedCollection)
    function Compare(Key1, Key2: Pointer): Integer; virtual;
    procedure ReOrder;
  end;

  TSortFunc = function(P1, P2: PFileRec): Integer;

  { dialog to handle file renaming }
  PRenameDialog = ^TRenameDialog;
  TRenameDialog = object(TDialog)
    TheName: PathStr;
    NewName: PathStr;
    constructor Init(const FileName:PathStr);
    function Valid(Command: Word): Boolean; virtual;
  end;

  { dialog to handle changing file attributes }
  PAttrDialog = ^TAttrDialog;
  TAttrDialog = object(TDialog)
    TheName: PathStr;
    NewAttr: Word;
    constructor Init(const FileName:PathStr);
    function Valid(Command: Word): Boolean; virtual;
  end;

  { TDeviceRec holds a single redirected device (net drives) }
  PDeviceRec = ^TDeviceRec;
  TDeviceRec = record
    LocalName: Char;
    NetworkName: PString;
  end;

  { TDeviceCollection is a collection of TDeviceRecs }
  PDeviceCollection = ^TDeviceCollection;
  TDeviceCollection = object(TCollection)
    procedure FreeItem(Item: Pointer); virtual;
  end;

procedure RegisterGlobals;
function WaitDialog(const Msg: String) : PDialog;

var
  RezFile: TResourceFile;
  RezStream: PStream;
  RezStrings: PStringList;

const

  ConfigRec: TConfigRec =
    (FileMask:'*.*'; ShowHidden:$00; SortField:$00; SortDir:$00;
     DisplayCase:$00; DisplayFields:$FF; Video:0);

  ConfirmDelete: Boolean = True;
  Viewer: PathStr = '';

  EXEName = 'TVFM.EXE';
  CFGExt  = '.CFG';
  TagChar = #251;

  UnwantedFiles: Word = VolumeID or Directory or SysFile or Hidden;

implementation

uses MsgBox, FileCopy, Equ;

const
  RHCStatusLine : TStreamRec = (
    ObjType : 100;
    VmtLink : Ofs(TypeOf(THCStatusLine)^);
    Load    : @THCStatusLine.Load;
    Store   : @THCStatusLine.Store
  );


{ ----------- General Purpose Routines -------------------- }

procedure RegisterGlobals;
begin
  RegisterType(RHCStatusLine);
end;

function WaitDialog(const Msg: String) : PDialog;
var
  R: TRect;
  D: PDialog;
  Width: Integer;
  XPos: Integer;
begin
  if Length(Msg) > 40 then Width := Length(Msg) + 4
  else Width := 40;
  XPos := (Width div 2) - (Length(Msg) div 2) - 1;

  R.Assign(0, 0, Width, 7);
  D := New(PDialog, Init(R, RezStrings^.Get(sPleaseWait)));
  with D^ do
  begin
    Options := Options or ofCentered;
    Flags := Flags and (not wfClose) and (not wfMove);
    R.Assign(XPos, 3, XPos+Length(Msg)+1, 4);
    Insert(New(PStaticText,Init(R, Msg)));
  end;
  WaitDialog := D;
end;

{ TTextCollection }
procedure TTextCollection.FreeItem(Item: pointer);
begin
  DisposeStr(Item);
end;


{ TProtectedStream }

procedure TProtectedStream.Error(Code, Info: Integer);
begin
  Writeln('Error in stream: Code = ', Code, ' Info = ', Info);
  Halt(1);
end;


{ THCStatusLine }

function THCStatusLine.Hint(AHelpCtx: Word) :String;
begin
  Hint := RezStrings^.Get(AHelpCtx);
end;

{ TFileRec }

constructor TFileRec.Init(const S: SearchRec);
var
  T: PathStr;
begin
  inherited Init;
  Tagged := False;
  FSplit(S.Name, T, Name, Ext);

  { fix up directory names without extensions }
  if (S.Attr and Directory <> 0) and (Name = '') then
  begin
    Name := Ext;
    Ext := '';
  end;
  Attr := S.Attr;
  Size := S.Size;
  Time := S.Time;
end;

procedure TFileRec.Toggle;
begin
  Tagged := not Tagged;
end;


{ Sort functions for TFileList }

function SortByName(P1, P2: PFileRec): Integer; far;
begin
  if P1^.Name < P2^.Name then SortByName := -1
  else if P1^.Name > P2^.Name then SortByName := 1
  else SortByName := 0;
end;

function SortByExt(P1, P2: PFileRec): Integer; far;
begin
  if P1^.Ext < P2^.Ext then SortByExt := -1
  else if P1^.Ext > P2^.Ext then SortByExt := 1
  else SortByExt := 0;
end;

function SortBySize(P1, P2: PFileRec): Integer; far;
begin
  if P1^.Size < P2^.Size then SortBySize := -1
  else if P1^.Size > P2^.Size then SortBySize := 1
  else SortBySize := 0;
end;

function SortByTime(P1, P2: PFileRec): Integer; far;
begin
  if P1^.Time < P2^.Time then SortByTime := -1
  else if P1^.Time > P2^.Time then SortByTime := 1
  else SortByTime := 0;
end;

{ TFileMover }
procedure TFileMover.Draw;
var
  B: TDrawBuffer;
  C: Word;
  F: PFileRec;
begin
  C := GetColor(1);
  { always draw at least the first entry in the collection }
  F := Items^.At(0);
  MoveChar(B, #32, C, Size.X);
  MoveStr(B, F^.Name + F^.Ext, C);
  WriteLine(0,0,Size.X,1,B);

  if Items^.Count > 1 then
  begin
    F := Items^.At(Items^.Count - 1);          { last item in list }
    MoveChar(B, #32, C, Size.X);
    MoveStr(B, F^.Name + F^.Ext, C);
    if Items^.Count > 2 then
    begin
      WriteLine(0,2,Size.X,1,B);
      if Items^.Count = 3 then
      begin
        F := Items^.At(1);
        MoveChar(B, #32, C, Size.X);
        MoveStr(B, F^.Name + F^.Ext, C);
      end
      else
      begin
        MoveChar(B, #32, C, Size.X);
        MoveChar(B[4], #250, C, 4);
      end;
      WriteLine(0,1,Size.X,1,B);
    end
    else
      WriteLine(0,1,Size.X,1,B);
  end;
end;

{ TFileList }

function TFileList.Compare(Key1, Key2: Pointer): Integer;
const
  Sorts : array[0..3] of TSortFunc =
   (SortByName, SortByExt, SortBySize, SortByTime);
var
  Result: Integer;
  I: Integer;
begin

  if Key2 = nil then
  begin
    Compare := 0;
    Exit;
  end;

  Result := Sorts[ConfigRec.SortField](Key1, Key2);
  I := 0;
  while (Result = 0) and (I <= 3) do
  begin
    Result := Sorts[I](Key1, Key2);
    Inc(I);
  end;

  { if the sort is descending, then reverse the Result variable }
  if (ConfigRec.SortDir <> 0) and (Result <> 0) then
    Result := Result * -1;

  Compare := Result;
end;

procedure TFileList.ReOrder;

procedure Sort(l, r: Integer);
var
  i, j: Integer;
  x, p: Pointer;
begin
  repeat
    i := l; j := r;
    x := KeyOf(Items^[(l + r) div 2]);
    repeat
      while Compare(KeyOf(Items^[i]), x) = -1 do Inc(i);
      while Compare(x, KeyOf(Items^[j])) = -1 do Dec(j);
      if i <= j then
      begin
	if i < j then
	begin
	  p := Items^[i];
	  Items^[i] := Items^[j];
	  Items^[j] := p;
	end;
	Inc(i); Dec(j);
      end;
    until i > j;
    if l < j then Sort(l, j);
    l := i;
  until l >= r;
end;

begin
  if Count > 1 then Sort(0, Count - 1);
end;


{ TRenameDialog }
constructor TRenameDialog.Init(const FileName: PathStr);
var
  R: TRect;
  P: PView;
  D: DirStr;
  N: NameStr;
  E: ExtStr;
begin
  R.Assign(0,0,40,7);
  inherited Init(R, 'Rename File');
  Options := Options or ofCentered;

  TheName := FileName;
  FSplit(TheName, D, N, E);
  D := N + E;
  R.Assign(2,2,18,3);
  Insert(New(PLabel, Init(R, '~' + D + '~ to ', nil)));
  R.Assign(19,2,33,3);
  Insert(New(PInputLine, Init(R, 12)));
  R.Assign(4,4,16,6);
  Insert(New(PButton, Init(R, '~O~K', cmOK, bfDefault)));
  R.Move(16,0);
  Insert(New(PButton, Init(R, '~C~ancel', cmCancel, bfNormal)));
  SelectNext(False);
  D := '';
  SetData(D);
end;

function TRenameDialog.Valid(Command: Word): Boolean;
var
  L: Longint;
  TheFile: File;
  D: DirStr;
  N: NameStr;
  E: ExtStr;
  I: Integer;
begin
  Valid := True;
  if (Command = cmCancel) or (Command = cmValid) then Exit;
  GetData(NewName);
  for I:= 1 to Length(NewName) do NewName[I] := UpCase(NewName[I]);
  FSplit(TheName, D, N, E);

  { check for duplicate name }
  if D + NewName = TheName then
  begin
    MessageBox(RezStrings^.Get(sSameNameErr), nil, mfError+mfOKButton);
    Valid := False;
    Exit;
  end;
  Assign(TheFile, TheName);
  {$I-}
  Rename(TheFile, D + NewName);
  {$I+}
  L := IOResult;
  if L <> 0 then
  begin
    MessageBox(RezStrings^.Get(sRenameErr), @L, mfError+mfOKButton);
    Valid := False;
  end;
end;

{ TAttrDialog }
constructor TAttrDialog.Init(const FileName:PathStr);
var
  R: TRect;
  P: PView;
  Attr: Word;
  XFer: Word;
  TheFile: File;
begin
  R.Assign(0,0,40,12);
  inherited Init(R, 'Change Attributes');
  Options := Options or ofCentered;

  TheName := FileName;
  Assign(TheFile, TheName);
  GetFAttr(TheFile, Attr);
  if DosError <> 0 then Fail;

  R.Assign(0,2,Length(FileName),3);
  P:=New(PStaticText, Init(R, FileName));
  P^.Options := P^.Options or ofCenterX;
  Insert(P);
  R.Assign(0,4,15,8);
  P := New(PCheckBoxes, Init(R, NewSItem('~A~rchive',
                                NewSItem('~R~ead-Only',
                                NewSItem('~S~ystem',
                                NewSItem('~H~idden',
                                nil))))));
  P^.Options := P^.Options or ofCenterX;
  Insert(P);
  R.Assign(4,9,16,11);
  Insert(New(PButton, Init(R, '~O~K', cmOK, bfDefault)));
  R.Move(16,0);
  Insert(New(PButton, Init(R, '~C~ancel', cmCancel, bfNormal)));
  SelectNext(False);
  XFer := 0;
  if Attr and Archive <> 0 then XFer := $01;
  if Attr and ReadOnly <> 0 then XFer := XFer or $02;
  if Attr and SysFile <> 0 then XFer := XFer or $04;
  if Attr and Hidden <> 0 then XFer := XFer or $08;
  SetData(XFer);
end;

function TAttrDialog.Valid(Command: Word): Boolean;
var
  XFer : Word;
  L: array[0..1] of Longint;
  TheFile: File;
begin
  Valid := True;
  if (Command = cmCancel) or (Command = cmValid) then Exit;
  GetData(XFer);
  NewAttr := 0;
  if XFer and $01 <> 0 then NewAttr := Archive;
  if XFer and $02 <> 0 then NewAttr := NewAttr or ReadOnly;
  if XFer and $04 <> 0 then NewAttr := NewAttr or SysFile;
  if XFer and $08 <> 0 then NewAttr := NewAttr or Hidden;
  Assign(TheFile, TheName);
  SetFAttr(TheFile, NewAttr);
  if DosError <> 0 then
  begin
    L[0] := DosError;
    L[1] := Longint(@TheName);
    MessageBox(RezStrings^.Get(sSetAttrErr), @L, mfError+mfOKButton);
    Valid := False;
  end;
end;

{ TDeviceCollection }
procedure TDeviceCollection.FreeItem(Item: Pointer);
var
  DeviceRec : PDeviceRec absolute Item;
begin
  DisposeStr(DeviceRec^.NetworkName);
  Dispose(DeviceRec);
end;


end. { unit }
